Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit b6a8ae9751db7fdb41adc0db6b1ac30e9689dfe3


Parents : 09d0fc7
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-04-16T20:14:35-05:00

feat(docs): update Raspberry Pi installation guide for MeshChatX with automated setup scripts and detailed service configuration instructions

Changes
Diff

diff --git a/docs/meshchatx_on_raspberry_pi.md b/docs/meshchatx_on_raspberry_pi.md
index bf7c5510..03d3477f 100644
--- a/docs/meshchatx_on_raspberry_pi.md
+++ b/docs/meshchatx_on_raspberry_pi.md
@@ -1,99 +1,232 @@
-# MeshChat on a Raspberry Pi
+# MeshChatX on Raspberry Pi
-A simple guide to install [MeshChat](https://github.com/liamcottle/reticulum-meshchat) on a Raspberry Pi.
+This guide shows a simple headless setup for running MeshChatX on a Raspberry Pi 4
+with a web UI you can access from another device on your network.
-This would allow you to connect an [RNode](https://github.com/markqvist/RNode_Firmware) (such as a Heltec v3) to the Rasbperry Pi via USB, and then access the MeshChat Web UI from another machine on your network.
+This install path uses a release wheel, which already includes frontend assets.
-My intended use case is to run the Pi + RNode combo from my solar-powered shed, and access the MeshChat Web UI via WiFi.
+## Automated Setup Scripts
-> Note: This has been tested on a Raspberry Pi 4 Model B
+If you want one-command setup, use the interactive installer from repo root:
-## Install Raspberry Pi OS
+```bash
+bash scripts/rpi/install_meshchatx.sh
+```
+
+The installer guides you through:
+
+- Optional `espeak-ng` install (tries apt/dnf/pacman)
+- Install method (`pipx` or `venv + pip`)
+- Storage and Reticulum directories
+- Bind host and port (with availability check)
+- HTTPS on/off (default on)
+- Service mode (`system`, `user`, or `none`)
+- Service startup verification (`Running on ...`)
+
+If startup validation fails, it prints recent logs and stops the service to avoid
+restart loops.
-If you haven't already done so, the first step is to install Raspberry Pi OS onto an sdcard, and then boot up the Pi. Once booted, follow the below commands.
+The installer also applies compatibility dependencies needed by older wheel
+releases.
-## Update System
+For all options:
+```bash
+bash scripts/rpi/install_meshchatx.sh --help
```
+
+## 1) Install Base Dependencies
+
+```bash
sudo apt update
-sudo apt upgrade
+sudo apt upgrade -y
+sudo apt install -y python3 python3-pip pipx
```
-## Install System Dependencies
+## 2) Enable pipx Path
-```
-sudo apt install git
-sudo apt install python3-pip
+```bash
+pipx ensurepath
+source ~/.profile
```
-## Install NodeJS v24
+If `pipx` is not available in your distro package repo, install it with:
+```bash
+python3 -m pip install --user pipx
+python3 -m pipx ensurepath
+source ~/.profile
```
-curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/nodesource.gpg
-NODE_MAJOR=24
-echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
-sudo apt update
-sudo apt install nodejs
+
+## 3) Install MeshChatX with pipx (recommended)
+
+Preferred option (recommended): install from a release wheel (4.4.0 or newer),
+because the wheel bundles frontend assets.
+
+```bash
+pipx install /path/to/reticulum_meshchatx-<version>-py3-none-any.whl
```
-## Install pnpm
+Direct example (v4.4.0):
+```bash
+pipx install "https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"
```
-corepack enable
-corepack prepare pnpm@latest --activate
+
+`py3-none-any` wheels are architecture-independent, so the same wheel artifact
+works on Raspberry Pi ARM and x86_64 Linux systems.
+
+Upgrade example:
+
+```bash
+pipx upgrade meshchatx
```
-## Install MeshChat
+## 4) Install MeshChatX without pipx (venv + pip)
+
+If you prefer not to use pipx:
+```bash
+mkdir -p ~/meshchatx
+cd ~/meshchatx
+python3 -m venv .venv
+source .venv/bin/activate
+python -m pip install --upgrade pip
+python -m pip install "https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"
```
-git clone https://github.com/liamcottle/reticulum-meshchat
-cd reticulum-meshchat
-pip install -r requirements.txt --break-system-packages
-pnpm install --prod
-pnpm run build-frontend
+
+Run command in venv mode:
+
+```bash
+~/meshchatx/.venv/bin/meshchatx --headless --host 0.0.0.0 --port 8000
```
-## Run MeshChat
+## 5) Run MeshChatX (Headless)
+```bash
+meshchatx --headless --host 0.0.0.0 --port 8000
```
-python meshchat.py --headless --host 0.0.0.0
+
+Then open:
+
+```text
+http://<pi-ip>:8000
```
-## Configure Service
+## 6) Configure a systemd Service
+
+`systemd` keeps MeshChatX running in the background and starts it automatically
+on boot.
+
+You have two service styles:
+
+- System service (`/etc/systemd/system/...`) for always-on host services.
+- User service (`~/.config/systemd/user/...`) for per-user sessions.
+
+### Option A: System service (recommended for Pi node/server use)
+
+Create `/etc/systemd/system/meshchatx.service`:
+
+```ini
+[Unit]
+Description=MeshChatX Headless (system service)
+After=network-online.target
+Wants=network-online.target
-Adding a `systemd` service will allow MeshChat to run in the background when you disconnect from the Pi's terminal.
+[Service]
+Type=simple
+User=pi
+Group=pi
+WorkingDirectory=/home/pi/meshchatx
+Environment="PATH=/home/pi/.local/bin:/usr/bin:/bin"
+ExecStart=/home/pi/.local/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir /home/pi/meshchatx/storage --reticulum-config-dir /home/pi/.reticulum
+Restart=always
+RestartSec=3
+[Install]
+WantedBy=multi-user.target
```
-sudo nano /etc/systemd/system/reticulum-meshchat.service
+
+The above service file is for pipx installs. For venv installs, use:
+
+```ini
+[Service]
+Type=simple
+User=pi
+Group=pi
+WorkingDirectory=/home/pi/meshchatx
+Environment="PATH=/home/pi/meshchatx/.venv/bin:/usr/bin:/bin"
+ExecStart=/home/pi/meshchatx/.venv/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir /home/pi/meshchatx/storage --reticulum-config-dir /home/pi/.reticulum
+Restart=always
+RestartSec=3
```
+Update `User`, `Group`, and paths if your install location is different.
+
+Enable and start:
+
+```bash
+mkdir -p /home/pi/meshchatx/storage /home/pi/.reticulum
+sudo chown -R pi:pi /home/pi/meshchatx
+sudo systemctl daemon-reload
+sudo systemctl enable --now meshchatx.service
+sudo systemctl status meshchatx.service
```
+
+### Option B: User service (no sudo system unit)
+
+Create `~/.config/systemd/user/meshchatx.service`:
+
+```ini
[Unit]
-Description=reticulum-meshchat
-After=network.target
-StartLimitIntervalSec=0
+Description=MeshChatX Headless (user service)
+After=network-online.target
[Service]
Type=simple
+WorkingDirectory=%h/meshchatx
+Environment="PATH=%h/.local/bin:/usr/bin:/bin"
+ExecStart=%h/.local/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir %h/meshchatx/storage --reticulum-config-dir %h/.reticulum
Restart=always
-RestartSec=1
-User=liamcottle
-Group=liamcottle
-WorkingDirectory=/home/liamcottle/reticulum-meshchat
-ExecStart=/usr/bin/env python /home/liamcottle/reticulum-meshchat/meshchat.py --headless --host 0.0.0.0
+RestartSec=3
[Install]
-WantedBy=multi-user.target
+WantedBy=default.target
+```
+
+Enable/start user service:
+
+```bash
+systemctl --user daemon-reload
+systemctl --user enable --now meshchatx.service
+systemctl --user status meshchatx.service
+```
+
+If you want user services to stay active without login:
+
+```bash
+sudo loginctl enable-linger pi
```
-> Note: Make sure to update the usernames in the service file if needed.
+### Service management commands
+```bash
+sudo systemctl restart meshchatx.service
+sudo systemctl stop meshchatx.service
+sudo systemctl disable meshchatx.service
```
-sudo systemctl enable reticulum-meshchat.service
-sudo systemctl start reticulum-meshchat.service
-sudo systemctl status reticulum-meshchat.service
+
+Useful logs and troubleshooting:
+
+```bash
+journalctl -u meshchatx.service -f
+journalctl -u meshchatx.service -n 200 --no-pager
+systemctl show meshchatx.service -p ExecStart -p User -p Group
```
-You should now be able to access MeshChat via your Pi's IP address.
+## Notes
-> Note: Don't forget to include the default port `8000`
+- Reticulum configuration and identity data are stored in the service user's home
+ directory by default (for example `~/.reticulum` and MeshChatX storage paths).
+- If you attach RNode hardware by USB, make sure the service user has permission
+ to access serial devices (`dialout` group on Debian-based systems).

diff --git a/meshchatx/src/frontend/public/meshchatx-docs/meshchatx_on_raspberry_pi.md b/meshchatx/src/frontend/public/meshchatx-docs/meshchatx_on_raspberry_pi.md
index 29a363b7..03d3477f 100644
--- a/meshchatx/src/frontend/public/meshchatx-docs/meshchatx_on_raspberry_pi.md
+++ b/meshchatx/src/frontend/public/meshchatx-docs/meshchatx_on_raspberry_pi.md
@@ -1,99 +1,232 @@
-# MeshChat on a Raspberry Pi
+# MeshChatX on Raspberry Pi
-A simple guide to install [MeshChat](https://github.com/liamcottle/reticulum-meshchat) on a Raspberry Pi.
+This guide shows a simple headless setup for running MeshChatX on a Raspberry Pi 4
+with a web UI you can access from another device on your network.
-This would allow you to connect an [RNode](https://github.com/markqvist/RNode_Firmware) (such as a Heltec v3) to the Rasbperry Pi via USB, and then access the MeshChat Web UI from another machine on your network.
+This install path uses a release wheel, which already includes frontend assets.
-My intended use case is to run the Pi + RNode combo from my solar-powered shed, and access the MeshChat Web UI via WiFi.
+## Automated Setup Scripts
-> Note: This has been tested on a Raspberry Pi 4 Model B
+If you want one-command setup, use the interactive installer from repo root:
-## Install Raspberry Pi OS
+```bash
+bash scripts/rpi/install_meshchatx.sh
+```
+
+The installer guides you through:
+
+- Optional `espeak-ng` install (tries apt/dnf/pacman)
+- Install method (`pipx` or `venv + pip`)
+- Storage and Reticulum directories
+- Bind host and port (with availability check)
+- HTTPS on/off (default on)
+- Service mode (`system`, `user`, or `none`)
+- Service startup verification (`Running on ...`)
+
+If startup validation fails, it prints recent logs and stops the service to avoid
+restart loops.
-If you haven't already done so, the first step is to install Raspberry Pi OS onto an sdcard, and then boot up the Pi. Once booted, follow the below commands.
+The installer also applies compatibility dependencies needed by older wheel
+releases.
-## Update System
+For all options:
+```bash
+bash scripts/rpi/install_meshchatx.sh --help
```
+
+## 1) Install Base Dependencies
+
+```bash
sudo apt update
-sudo apt upgrade
+sudo apt upgrade -y
+sudo apt install -y python3 python3-pip pipx
```
-## Install System Dependencies
+## 2) Enable pipx Path
-```
-sudo apt install git
-sudo apt install python3-pip
+```bash
+pipx ensurepath
+source ~/.profile
```
-## Install NodeJS v22
+If `pipx` is not available in your distro package repo, install it with:
+```bash
+python3 -m pip install --user pipx
+python3 -m pipx ensurepath
+source ~/.profile
```
-curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/nodesource.gpg
-NODE_MAJOR=24
-echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
-sudo apt update
-sudo apt install nodejs
+
+## 3) Install MeshChatX with pipx (recommended)
+
+Preferred option (recommended): install from a release wheel (4.4.0 or newer),
+because the wheel bundles frontend assets.
+
+```bash
+pipx install /path/to/reticulum_meshchatx-<version>-py3-none-any.whl
```
-## Install pnpm
+Direct example (v4.4.0):
+```bash
+pipx install "https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"
```
-corepack enable
-corepack prepare pnpm@latest --activate
+
+`py3-none-any` wheels are architecture-independent, so the same wheel artifact
+works on Raspberry Pi ARM and x86_64 Linux systems.
+
+Upgrade example:
+
+```bash
+pipx upgrade meshchatx
```
-## Install MeshChat
+## 4) Install MeshChatX without pipx (venv + pip)
+
+If you prefer not to use pipx:
+```bash
+mkdir -p ~/meshchatx
+cd ~/meshchatx
+python3 -m venv .venv
+source .venv/bin/activate
+python -m pip install --upgrade pip
+python -m pip install "https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"
```
-git clone https://github.com/liamcottle/reticulum-meshchat
-cd reticulum-meshchat
-pip install -r requirements.txt --break-system-packages
-pnpm install --prod
-pnpm run build-frontend
+
+Run command in venv mode:
+
+```bash
+~/meshchatx/.venv/bin/meshchatx --headless --host 0.0.0.0 --port 8000
```
-## Run MeshChat
+## 5) Run MeshChatX (Headless)
+```bash
+meshchatx --headless --host 0.0.0.0 --port 8000
```
-python meshchat.py --headless --host 0.0.0.0
+
+Then open:
+
+```text
+http://<pi-ip>:8000
```
-## Configure Service
+## 6) Configure a systemd Service
+
+`systemd` keeps MeshChatX running in the background and starts it automatically
+on boot.
+
+You have two service styles:
+
+- System service (`/etc/systemd/system/...`) for always-on host services.
+- User service (`~/.config/systemd/user/...`) for per-user sessions.
+
+### Option A: System service (recommended for Pi node/server use)
+
+Create `/etc/systemd/system/meshchatx.service`:
+
+```ini
+[Unit]
+Description=MeshChatX Headless (system service)
+After=network-online.target
+Wants=network-online.target
-Adding a `systemd` service will allow MeshChat to run in the background when you disconnect from the Pi's terminal.
+[Service]
+Type=simple
+User=pi
+Group=pi
+WorkingDirectory=/home/pi/meshchatx
+Environment="PATH=/home/pi/.local/bin:/usr/bin:/bin"
+ExecStart=/home/pi/.local/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir /home/pi/meshchatx/storage --reticulum-config-dir /home/pi/.reticulum
+Restart=always
+RestartSec=3
+[Install]
+WantedBy=multi-user.target
```
-sudo nano /etc/systemd/system/reticulum-meshchat.service
+
+The above service file is for pipx installs. For venv installs, use:
+
+```ini
+[Service]
+Type=simple
+User=pi
+Group=pi
+WorkingDirectory=/home/pi/meshchatx
+Environment="PATH=/home/pi/meshchatx/.venv/bin:/usr/bin:/bin"
+ExecStart=/home/pi/meshchatx/.venv/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir /home/pi/meshchatx/storage --reticulum-config-dir /home/pi/.reticulum
+Restart=always
+RestartSec=3
```
+Update `User`, `Group`, and paths if your install location is different.
+
+Enable and start:
+
+```bash
+mkdir -p /home/pi/meshchatx/storage /home/pi/.reticulum
+sudo chown -R pi:pi /home/pi/meshchatx
+sudo systemctl daemon-reload
+sudo systemctl enable --now meshchatx.service
+sudo systemctl status meshchatx.service
```
+
+### Option B: User service (no sudo system unit)
+
+Create `~/.config/systemd/user/meshchatx.service`:
+
+```ini
[Unit]
-Description=reticulum-meshchat
-After=network.target
-StartLimitIntervalSec=0
+Description=MeshChatX Headless (user service)
+After=network-online.target
[Service]
Type=simple
+WorkingDirectory=%h/meshchatx
+Environment="PATH=%h/.local/bin:/usr/bin:/bin"
+ExecStart=%h/.local/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir %h/meshchatx/storage --reticulum-config-dir %h/.reticulum
Restart=always
-RestartSec=1
-User=liamcottle
-Group=liamcottle
-WorkingDirectory=/home/liamcottle/reticulum-meshchat
-ExecStart=/usr/bin/env python /home/liamcottle/reticulum-meshchat/meshchat.py --headless --host 0.0.0.0
+RestartSec=3
[Install]
-WantedBy=multi-user.target
+WantedBy=default.target
+```
+
+Enable/start user service:
+
+```bash
+systemctl --user daemon-reload
+systemctl --user enable --now meshchatx.service
+systemctl --user status meshchatx.service
+```
+
+If you want user services to stay active without login:
+
+```bash
+sudo loginctl enable-linger pi
```
-> Note: Make sure to update the usernames in the service file if needed.
+### Service management commands
+```bash
+sudo systemctl restart meshchatx.service
+sudo systemctl stop meshchatx.service
+sudo systemctl disable meshchatx.service
```
-sudo systemctl enable reticulum-meshchat.service
-sudo systemctl start reticulum-meshchat.service
-sudo systemctl status reticulum-meshchat.service
+
+Useful logs and troubleshooting:
+
+```bash
+journalctl -u meshchatx.service -f
+journalctl -u meshchatx.service -n 200 --no-pager
+systemctl show meshchatx.service -p ExecStart -p User -p Group
```
-You should now be able to access MeshChat via your Pi's IP address.
+## Notes
-> Note: Don't forget to include the default port `8000`
+- Reticulum configuration and identity data are stored in the service user's home
+ directory by default (for example `~/.reticulum` and MeshChatX storage paths).
+- If you attach RNode hardware by USB, make sure the service user has permission
+ to access serial devices (`dialout` group on Debian-based systems).

diff --git a/scripts/rpi/install_meshchatx.sh b/scripts/rpi/install_meshchatx.sh
new file mode 100755
index 00000000..0bad8a48
--- /dev/null
+++ b/scripts/rpi/install_meshchatx.sh
@@ -0,0 +1,494 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+DEFAULT_WHEEL_URL="https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"
+
+RUN_USER="${SUDO_USER:-$USER}"
+RUN_GROUP="$(id -gn "$RUN_USER")"
+USER_HOME="$(eval echo "~${RUN_USER}")"
+
+if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
+ C_RESET="$(tput sgr0)"
+ C_BOLD="$(tput bold)"
+ C_RED="$(tput setaf 1)"
+ C_GREEN="$(tput setaf 2)"
+ C_YELLOW="$(tput setaf 3)"
+ C_BLUE="$(tput setaf 4)"
+else
+ C_RESET=""
+ C_BOLD=""
+ C_RED=""
+ C_GREEN=""
+ C_YELLOW=""
+ C_BLUE=""
+fi
+
+note() {
+ echo "${C_BLUE}${C_BOLD}==>${C_RESET} $*"
+}
+
+warn() {
+ echo "${C_YELLOW}${C_BOLD}WARN:${C_RESET} $*"
+}
+
+err() {
+ echo "${C_RED}${C_BOLD}ERROR:${C_RESET} $*" >&2
+}
+
+ok() {
+ echo "${C_GREEN}${C_BOLD}OK:${C_RESET} $*"
+}
+
+run_as_user() {
+ local cmd="$1"
+ if [[ "$EUID" -eq 0 && "$RUN_USER" != "root" ]]; then
+ sudo -u "$RUN_USER" -H bash -lc "$cmd"
+ else
+ bash -lc "$cmd"
+ fi
+}
+
+prompt_default() {
+ local prompt="$1"
+ local default="$2"
+ local value=""
+ read -r -p "$prompt [$default]: " value
+ if [[ -z "$value" ]]; then
+ value="$default"
+ fi
+ echo "$value"
+}
+
+prompt_yes_no() {
+ local prompt="$1"
+ local default="${2:-y}"
+ local answer=""
+ local hint="Y/n"
+ if [[ "$default" == "n" ]]; then
+ hint="y/N"
+ fi
+
+ while true; do
+ read -r -p "$prompt ($hint): " answer
+ if [[ -z "$answer" ]]; then
+ answer="$default"
+ fi
+ case "${answer,,}" in
+ y|yes) return 0 ;;
+ n|no) return 1 ;;
+ *)
+ echo "Please answer y or n."
+ ;;
+ esac
+ done
+}
+
+pick_package_manager() {
+ if command -v apt-get >/dev/null 2>&1; then
+ echo "apt"
+ return
+ fi
+ if command -v dnf >/dev/null 2>&1; then
+ echo "dnf"
+ return
+ fi
+ if command -v pacman >/dev/null 2>&1; then
+ echo "pacman"
+ return
+ fi
+ echo "none"
+}
+
+install_package_if_possible() {
+ local package="$1"
+ local mgr
+ mgr="$(pick_package_manager)"
+ case "$mgr" in
+ apt)
+ if [[ "$EUID" -eq 0 ]]; then
+ apt-get update && apt-get install -y "$package"
+ else
+ sudo apt-get update && sudo apt-get install -y "$package"
+ fi
+ ;;
+ dnf)
+ if [[ "$EUID" -eq 0 ]]; then
+ dnf install -y "$package"
+ else
+ sudo dnf install -y "$package"
+ fi
+ ;;
+ pacman)
+ if [[ "$EUID" -eq 0 ]]; then
+ pacman -Sy --noconfirm "$package"
+ else
+ sudo pacman -Sy --noconfirm "$package"
+ fi
+ ;;
+ none)
+ warn "No supported package manager found (apt/dnf/pacman). Skipping install for $package."
+ ;;
+ esac
+}
+
+check_port_available() {
+ local host="$1"
+ local port="$2"
+ python3 - "$host" "$port" <<'PY'
+import socket
+import sys
+
+host = sys.argv[1]
+port = int(sys.argv[2])
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+try:
+ s.bind((host, port))
+except OSError:
+ sys.exit(1)
+finally:
+ s.close()
+sys.exit(0)
+PY
+}
+
+detect_arch() {
+ python3 - <<'PY'
+import platform
+print(platform.machine().strip().lower())
+PY
+}
+
+is_supported_rpi_arch() {
+ local arch="$1"
+ case "$arch" in
+ armv6l|armv7l|aarch64|arm64)
+ return 0
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+}
+
+write_system_service() {
+ local exec_cmd="$1"
+ local workdir="$2"
+ local path_value="$3"
+ local svc="/etc/systemd/system/meshchatx.service"
+
+ if [[ "$EUID" -eq 0 ]]; then
+ SUDO=""
+ else
+ SUDO="sudo"
+ fi
+
+ $SUDO tee "$svc" >/dev/null <<EOF
+[Unit]
+Description=MeshChatX Headless (system service)
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+User=${RUN_USER}
+Group=${RUN_GROUP}
+WorkingDirectory=${workdir}
+Environment="PATH=${path_value}"
+ExecStart=${exec_cmd}
+Restart=always
+RestartSec=3
+
+[Install]
+WantedBy=multi-user.target
+EOF
+}
+
+write_user_service() {
+ local exec_cmd="$1"
+ local workdir="$2"
+ local path_value="$3"
+ local svc_path="${USER_HOME}/.config/systemd/user/meshchatx.service"
+
+ run_as_user "mkdir -p '${USER_HOME}/.config/systemd/user'"
+ run_as_user "cat > '${svc_path}' <<'EOF'
+[Unit]
+Description=MeshChatX Headless (user service)
+After=network-online.target
+
+[Service]
+Type=simple
+WorkingDirectory=${workdir}
+Environment=\"PATH=${path_value}\"
+ExecStart=${exec_cmd}
+Restart=always
+RestartSec=3
+
+[Install]
+WantedBy=default.target
+EOF"
+}
+
+handle_service_start_failure() {
+ local mode="$1"
+ local reason="$2"
+
+ err "$reason"
+ warn "Service startup failed. Recent logs:"
+ if [[ "$mode" == "system" ]]; then
+ sudo journalctl -u meshchatx.service -n 200 --no-pager || true
+ sudo systemctl stop meshchatx.service || true
+ sudo systemctl reset-failed meshchatx.service || true
+ else
+ run_as_user "journalctl --user -u meshchatx.service -n 200 --no-pager" || true
+ run_as_user "systemctl --user stop meshchatx.service || true"
+ run_as_user "systemctl --user reset-failed meshchatx.service || true"
+ fi
+ err "Service was stopped/reset. Fix config and run installer again."
+ exit 1
+}
+
+verify_service_started() {
+ local mode="$1"
+ local probe_host="$2"
+ local probe_port="$3"
+ local https_enabled="$4"
+ local tries=40
+ local log_cmd=""
+ local stop_cmd=""
+ local scheme="https"
+ if [[ "$https_enabled" == "no" ]]; then
+ scheme="http"
+ fi
+
+ if [[ "$mode" == "system" ]]; then
+ log_cmd="sudo journalctl -u meshchatx.service -n 200 --no-pager"
+ stop_cmd="sudo systemctl stop meshchatx.service; sudo systemctl reset-failed meshchatx.service"
+ else
+ log_cmd="journalctl --user -u meshchatx.service -n 200 --no-pager"
+ stop_cmd="systemctl --user stop meshchatx.service || true; systemctl --user reset-failed meshchatx.service || true"
+ fi
+
+ note "Verifying service startup via ${scheme}://${probe_host}:${probe_port}/api/v1/status ..."
+ for _ in $(seq 1 "$tries"); do
+ if python3 - "$scheme" "$probe_host" "$probe_port" <<'PY'
+import json
+import ssl
+import sys
+import urllib.request
+
+scheme, host, port = sys.argv[1], sys.argv[2], int(sys.argv[3])
+url = f"{scheme}://{host}:{port}/api/v1/status"
+
+ctx = ssl._create_unverified_context() if scheme == "https" else None
+req = urllib.request.Request(url, method="GET")
+try:
+ with urllib.request.urlopen(req, timeout=2, context=ctx) as resp:
+ if resp.status != 200:
+ raise RuntimeError(f"unexpected status {resp.status}")
+ data = json.loads(resp.read().decode("utf-8"))
+ if data.get("status") != "ok":
+ raise RuntimeError("status payload is not ok")
+except Exception:
+ sys.exit(1)
+sys.exit(0)
+PY
+ then
+ ok "Service started successfully (status endpoint is healthy)."
+ return 0
+ fi
+ sleep 1
+ done
+
+ err "Service did not pass status endpoint health check."
+ warn "Recent logs:"
+ if [[ "$mode" == "system" ]]; then
+ sudo journalctl -u meshchatx.service -n 200 --no-pager || true
+ eval "$stop_cmd"
+ else
+ run_as_user "$log_cmd" || true
+ run_as_user "$stop_cmd"
+ fi
+ err "Service was stopped to prevent restart loops."
+ return 1
+}
+
+main() {
+ note "MeshChatX Raspberry Pi Interactive Installer"
+ echo "Detected user: ${RUN_USER} (group: ${RUN_GROUP})"
+ local arch
+ arch="$(detect_arch)"
+ echo "Detected architecture: ${arch}"
+ if is_supported_rpi_arch "$arch"; then
+ ok "Detected Raspberry Pi ARM architecture (${arch})."
+ else
+ warn "Detected non-RPi arch (${arch}). Script can still run, but this guide targets Raspberry Pi ARM."
+ fi
+ note "The wheel is py3-none-any, so it is architecture-independent (32-bit and 64-bit ARM are supported)."
+ echo
+
+ if prompt_yes_no "Do you want to install espeak-ng?" "y"; then
+ note "Installing espeak-ng (best effort)..."
+ if ! install_package_if_possible "espeak-ng"; then
+ warn "Could not install espeak-ng automatically; continuing."
+ fi
+ else
+ note "Skipping espeak-ng installation."
+ fi
+
+ local method_choice=""
+ while [[ "$method_choice" != "1" && "$method_choice" != "2" ]]; do
+ echo
+ echo "Choose installation method:"
+ echo " 1) pipx (recommended)"
+ echo " 2) venv + pip"
+ read -r -p "Selection [1/2]: " method_choice
+ if [[ -z "$method_choice" ]]; then
+ method_choice="1"
+ fi
+ done
+
+ local wheel_url
+ wheel_url="$(prompt_default "Wheel URL" "$DEFAULT_WHEEL_URL")"
+
+ local install_root
+ install_root="$(prompt_default "Install root directory" "${USER_HOME}/meshchatx")"
+ local storage_dir
+ storage_dir="$(prompt_default "Storage directory" "${install_root}/storage")"
+ local rns_dir
+ rns_dir="$(prompt_default "Reticulum config directory" "${USER_HOME}/.reticulum")"
+ local bind_host
+ bind_host="$(prompt_default "Bind IP/host" "0.0.0.0")"
+ local bind_port
+ bind_port="$(prompt_default "Bind port" "8000")"
+
+ if check_port_available "$bind_host" "$bind_port"; then
+ ok "Port ${bind_port} is available on ${bind_host}."
+ else
+ warn "Port ${bind_port} appears to be in use on ${bind_host}."
+ if ! prompt_yes_no "Continue anyway?" "n"; then
+ err "Aborted."
+ exit 1
+ fi
+ fi
+
+ local https_enabled="yes"
+ if ! prompt_yes_no "Enable HTTPS?" "y"; then
+ https_enabled="no"
+ fi
+
+ local service_mode="none"
+ if prompt_yes_no "Do you want to configure a systemd service?" "y"; then
+ service_mode=""
+ while [[ "$service_mode" != "system" && "$service_mode" != "user" ]]; do
+ service_mode="$(prompt_default "Service mode (system/user)" "system")"
+ done
+ fi
+
+ local no_https_flag=""
+ if [[ "$https_enabled" == "no" ]]; then
+ no_https_flag=" --no-https"
+ fi
+
+ local probe_host="$bind_host"
+ if [[ "$bind_host" == "0.0.0.0" ]]; then
+ probe_host="127.0.0.1"
+ elif [[ "$bind_host" == "::" ]]; then
+ probe_host="::1"
+ fi
+
+ note "Preparing directories..."
+ if [[ "$EUID" -eq 0 ]]; then
+ install -d -m 755 -o "$RUN_USER" -g "$RUN_GROUP" "$install_root" "$storage_dir" "$rns_dir"
+ else
+ mkdir -p "$install_root" "$storage_dir" "$rns_dir"
+ if command -v sudo >/dev/null 2>&1; then
+ sudo install -d -m 755 -o "$RUN_USER" -g "$RUN_GROUP" "$install_root" "$storage_dir" "$rns_dir" >/dev/null 2>&1 || true
+ fi
+ fi
+
+ local bin_path=""
+ local venv_path="${install_root}/.venv"
+
+ if [[ "$method_choice" == "1" ]]; then
+ if ! command -v pipx >/dev/null 2>&1; then
+ err "pipx not found. Install pipx first or choose venv method."
+ exit 1
+ fi
+ note "Installing MeshChatX via pipx..."
+ run_as_user "pipx ensurepath >/dev/null 2>&1 || true"
+ run_as_user "pipx install --force '${wheel_url}'"
+ run_as_user "pipx inject reticulum-meshchatx packaging >/dev/null 2>&1 || true"
+ bin_path="${USER_HOME}/.local/bin/meshchatx"
+ else
+ note "Installing MeshChatX via venv + pip..."
+ run_as_user "python3 -m venv '${venv_path}'"
+ run_as_user "'${venv_path}/bin/python' -m pip install --upgrade pip"
+ run_as_user "'${venv_path}/bin/python' -m pip install '${wheel_url}'"
+ run_as_user "'${venv_path}/bin/python' -m pip install packaging"
+ bin_path="${venv_path}/bin/meshchatx"
+ fi
+
+ if [[ ! -x "$bin_path" ]]; then
+ err "meshchatx binary not found at: $bin_path"
+ exit 1
+ fi
+
+ local exec_cmd="${bin_path} --headless --host ${bind_host} --port ${bind_port} --storage-dir ${storage_dir} --reticulum-config-dir ${rns_dir}${no_https_flag}"
+ local path_env="${venv_path}/bin:${USER_HOME}/.local/bin:/usr/bin:/bin"
+
+ if [[ "$service_mode" == "none" ]]; then
+ ok "Install complete."
+ echo
+ echo "Run command:"
+ echo " ${exec_cmd}"
+ exit 0
+ fi
+
+ if [[ "$service_mode" == "system" ]]; then
+ note "Creating system service..."
+ write_system_service "$exec_cmd" "$install_root" "$path_env"
+ if [[ "$EUID" -eq 0 ]]; then
+ if ! systemctl daemon-reload; then
+ handle_service_start_failure "system" "systemctl daemon-reload failed."
+ fi
+ if ! systemctl enable --now meshchatx.service; then
+ handle_service_start_failure "system" "systemctl enable/start failed."
+ fi
+ else
+ if ! sudo systemctl daemon-reload; then
+ handle_service_start_failure "system" "sudo systemctl daemon-reload failed."
+ fi
+ if ! sudo systemctl enable --now meshchatx.service; then
+ handle_service_start_failure "system" "sudo systemctl enable/start failed."
+ fi
+ fi
+ if ! verify_service_started "system" "$probe_host" "$bind_port" "$https_enabled"; then
+ exit 1
+ fi
+ ok "System service is enabled and running."
+ else
+ if [[ "$service_mode" == "user" ]]; then
+ note "Creating user service..."
+ write_user_service "$exec_cmd" "$install_root" "$path_env"
+ if ! run_as_user "systemctl --user daemon-reload"; then
+ handle_service_start_failure "user" "systemctl --user daemon-reload failed."
+ fi
+ if ! run_as_user "systemctl --user enable --now meshchatx.service"; then
+ handle_service_start_failure "user" "systemctl --user enable/start failed."
+ fi
+ if ! verify_service_started "user" "$probe_host" "$bind_port" "$https_enabled"; then
+ exit 1
+ fi
+ ok "User service is enabled and running."
+ fi
+ fi
+
+ echo
+ echo "Web UI:"
+ echo " https://${bind_host}:${bind_port}"
+ if [[ "$https_enabled" == "no" ]]; then
+ echo " (HTTP mode enabled)"
+ fi
+}
+
+main "$@"


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────